/* Redis CLI (command line interface)
 *
 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "fmacros.h"
#include "version.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>

#include <hiredis.h>
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#include "dict.h"
#include "adlist.h"
#include "zmalloc.h"
#include "linenoise.h"
#include "help.h"
#include "anet.h"
#include "ae.h"

#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
#define CLUSTER_MANAGER_MIGRATE_PIPELINE 10
#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2

#define OUTPUT_STANDARD 0
#define OUTPUT_RAW 1
#define OUTPUT_CSV 2
#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"

/* Cluster Manager Command Info */
typedef struct clusterManagerCommand
{
    char *name;
    int argc;
    char **argv;
    int flags;
    int replicas;
    char *from;
    char *to;
    char **weight;
    int weight_argc;
    char *master_id;
    int slots;
    int timeout;
    int pipeline;
    float threshold;
} clusterManagerCommand;

static struct config
{
    char *hostip;
    int hostport;
    char *hostsocket;
    long repeat;
    long interval;
    int dbnum;
    int interactive;
    int shutdown;
    int monitor_mode;
    int pubsub_mode;
    int latency_mode;
    int latency_dist_mode;
    int latency_history;
    int lru_test_mode;
    long long lru_test_sample_size;
    int cluster_mode;
    int cluster_reissue_command;
    int slave_mode;
    int pipe_mode;
    int pipe_timeout;
    int getrdb_mode;
    int stat_mode;
    int scan_mode;
    int intrinsic_latency_mode;
    int intrinsic_latency_duration;
    char *pattern;
    char *rdb_filename;
    int bigkeys;
    int hotkeys;
    int stdinarg; /* get last arg from stdin. (-x option) */
    char *auth;
    int output; /* output mode, see OUTPUT_* defines */
    sds mb_delim;
    char prompt[128];
    char *eval;
    int eval_ldb;
    int eval_ldb_sync;      /* Ask for synchronous mode of the Lua debugger. */
    int eval_ldb_end;       /* Lua debugging session ended. */
    int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
    int last_cmd_type;
    int verbose;
    clusterManagerCommand cluster_manager_command;
    int no_auth_warning;
} config;

/* User preferences. */
static struct pref
{
    int hints;
} pref;

/* The actual palette in use. */
int *spectrum_palette;
int spectrum_palette_size;

/* --latency-dist palettes. */
int spectrum_palette_color_size = 19;
int spectrum_palette_color[] = {0, 233, 234, 235, 237, 239, 241, 243, 245, 247, 144, 143, 142, 184, 226, 214, 208, 202, 196};

static redisContext *context;

/*------------------------------------------------------------------------------
 * User interface
 *--------------------------------------------------------------------------- */

static int parseOptions(int argc, char **argv)
{
    int i;

    for (i = 1; i < argc; i++)
    {
        int lastarg = i == argc - 1;

        if (!strcmp(argv[i], "-h") && !lastarg)
        {
            sdsfree(config.hostip);
            config.hostip = sdsnew(argv[++i]);
        }
        else if (!strcmp(argv[i], "-h") && lastarg)
        {
            usage();
        }
        else if (!strcmp(argv[i], "--help"))
        {
            usage();
        }
        else if (!strcmp(argv[i], "-x"))
        {
            config.stdinarg = 1;
        }
        else if (!strcmp(argv[i], "-p") && !lastarg)
        {
            config.hostport = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "-s") && !lastarg)
        {
            config.hostsocket = argv[++i];
        }
        else if (!strcmp(argv[i], "-r") && !lastarg)
        {
            config.repeat = strtoll(argv[++i], NULL, 10);
        }
        else if (!strcmp(argv[i], "-i") && !lastarg)
        {
            double seconds = atof(argv[++i]);
            config.interval = seconds * 1000000;
        }
        else if (!strcmp(argv[i], "-n") && !lastarg)
        {
            config.dbnum = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--no-auth-warning"))
        {
            config.no_auth_warning = 1;
        }
        else if (!strcmp(argv[i], "-a") && !lastarg)
        {
            config.auth = argv[++i];
        }
        else if (!strcmp(argv[i], "-u") && !lastarg)
        {
            parseRedisUri(argv[++i]);
        }
        else if (!strcmp(argv[i], "--raw"))
        {
            config.output = OUTPUT_RAW;
        }
        else if (!strcmp(argv[i], "--no-raw"))
        {
            config.output = OUTPUT_STANDARD;
        }
        else if (!strcmp(argv[i], "--csv"))
        {
            config.output = OUTPUT_CSV;
        }
        else if (!strcmp(argv[i], "--latency"))
        {
            config.latency_mode = 1;
        }
        else if (!strcmp(argv[i], "--latency-dist"))
        {
            config.latency_dist_mode = 1;
        }
        else if (!strcmp(argv[i], "--mono"))
        {
            spectrum_palette = spectrum_palette_mono;
            spectrum_palette_size = spectrum_palette_mono_size;
        }
        else if (!strcmp(argv[i], "--latency-history"))
        {
            config.latency_mode = 1;
            config.latency_history = 1;
        }
        else if (!strcmp(argv[i], "--lru-test") && !lastarg)
        {
            config.lru_test_mode = 1;
            config.lru_test_sample_size = strtoll(argv[++i], NULL, 10);
        }
        else if (!strcmp(argv[i], "--slave"))
        {
            config.slave_mode = 1;
        }
        else if (!strcmp(argv[i], "--replica"))
        {
            config.slave_mode = 1;
        }
        else if (!strcmp(argv[i], "--stat"))
        {
            config.stat_mode = 1;
        }
        else if (!strcmp(argv[i], "--scan"))
        {
            config.scan_mode = 1;
        }
        else if (!strcmp(argv[i], "--pattern") && !lastarg)
        {
            config.pattern = argv[++i];
        }
        else if (!strcmp(argv[i], "--intrinsic-latency") && !lastarg)
        {
            config.intrinsic_latency_mode = 1;
            config.intrinsic_latency_duration = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--rdb") && !lastarg)
        {
            config.getrdb_mode = 1;
            config.rdb_filename = argv[++i];
        }
        else if (!strcmp(argv[i], "--pipe"))
        {
            config.pipe_mode = 1;
        }
        else if (!strcmp(argv[i], "--pipe-timeout") && !lastarg)
        {
            config.pipe_timeout = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--bigkeys"))
        {
            config.bigkeys = 1;
        }
        else if (!strcmp(argv[i], "--hotkeys"))
        {
            config.hotkeys = 1;
        }
        else if (!strcmp(argv[i], "--eval") && !lastarg)
        {
            config.eval = argv[++i];
        }
        else if (!strcmp(argv[i], "--ldb"))
        {
            config.eval_ldb = 1;
            config.output = OUTPUT_RAW;
        }
        else if (!strcmp(argv[i], "--ldb-sync-mode"))
        {
            config.eval_ldb = 1;
            config.eval_ldb_sync = 1;
            config.output = OUTPUT_RAW;
        }
        else if (!strcmp(argv[i], "-c"))
        {
            config.cluster_mode = 1;
        }
        else if (!strcmp(argv[i], "-d") && !lastarg)
        {
            sdsfree(config.mb_delim);
            config.mb_delim = sdsnew(argv[++i]);
        }
        else if (!strcmp(argv[i], "--verbose"))
        {
            config.verbose = 1;
        }
        else if (!strcmp(argv[i], "--cluster") && !lastarg)
        {
            if (CLUSTER_MANAGER_MODE())
                usage();
            char *cmd = argv[++i];
            int j = i;
            while (j < argc && argv[j][0] != '-')
                j++;
            if (j > i)
                j--;
            createClusterManagerCommand(cmd, j - i, argv + i + 1);
            i = j;
        }
        else if (!strcmp(argv[i], "--cluster") && lastarg)
        {
            usage();
        }
        else if (!strcmp(argv[i], "--cluster-replicas") && !lastarg)
        {
            config.cluster_manager_command.replicas = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--cluster-master-id") && !lastarg)
        {
            config.cluster_manager_command.master_id = argv[++i];
        }
        else if (!strcmp(argv[i], "--cluster-from") && !lastarg)
        {
            config.cluster_manager_command.from = argv[++i];
        }
        else if (!strcmp(argv[i], "--cluster-to") && !lastarg)
        {
            config.cluster_manager_command.to = argv[++i];
        }
        else if (!strcmp(argv[i], "--cluster-weight") && !lastarg)
        {
            if (config.cluster_manager_command.weight != NULL)
            {
                fprintf(stderr, "WARNING: you cannot use --cluster-weight "
                                "more than once.\n"
                                "You can set more weights by adding them "
                                "as a space-separated list, ie:\n"
                                "--cluster-weight n1=w n2=w\n");
                exit(1);
            }
            int widx = i + 1;
            char **weight = argv + widx;
            int wargc = 0;
            for (; widx < argc; widx++)
            {
                if (strstr(argv[widx], "--") == argv[widx])
                    break;
                if (strchr(argv[widx], '=') == NULL)
                    break;
                wargc++;
            }
            if (wargc > 0)
            {
                config.cluster_manager_command.weight = weight;
                config.cluster_manager_command.weight_argc = wargc;
                i += wargc;
            }
        }
        else if (!strcmp(argv[i], "--cluster-slots") && !lastarg)
        {
            config.cluster_manager_command.slots = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--cluster-timeout") && !lastarg)
        {
            config.cluster_manager_command.timeout = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--cluster-pipeline") && !lastarg)
        {
            config.cluster_manager_command.pipeline = atoi(argv[++i]);
        }
        else if (!strcmp(argv[i], "--cluster-threshold") && !lastarg)
        {
            config.cluster_manager_command.threshold = atof(argv[++i]);
        }
        else if (!strcmp(argv[i], "--cluster-yes"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_YES;
        }
        else if (!strcmp(argv[i], "--cluster-simulate"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
        }
        else if (!strcmp(argv[i], "--cluster-replace"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_REPLACE;
        }
        else if (!strcmp(argv[i], "--cluster-copy"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_COPY;
        }
        else if (!strcmp(argv[i], "--cluster-slave"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_SLAVE;
        }
        else if (!strcmp(argv[i], "--cluster-use-empty-masters"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
        }
        else if (!strcmp(argv[i], "--cluster-search-multiple-owners"))
        {
            config.cluster_manager_command.flags |=
                CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
        }
        else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version"))
        {
            sds version = cliVersion();
            printf("redis-cli %s\n", version);
            sdsfree(version);
            exit(0);
        }
        else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-')
        {
            if (config.cluster_manager_command.argc == 0)
            {
                int j = i + 1;
                while (j < argc && argv[j][0] != '-')
                    j++;
                int cmd_argc = j - i;
                config.cluster_manager_command.argc = cmd_argc;
                config.cluster_manager_command.argv = argv + i;
                if (cmd_argc > 1)
                    i = j - 1;
            }
        }
        else
        {
            if (argv[i][0] == '-')
            {
                fprintf(stderr,
                        "Unrecognized option or bad number of args for: '%s'\n",
                        argv[i]);
                exit(1);
            }
            else
            {
                /* Likely the command name, stop here. */
                break;
            }
        }
    }

    /* --ldb requires --eval. */
    if (config.eval_ldb && config.eval == NULL)
    {
        fprintf(stderr, "Options --ldb and --ldb-sync-mode require --eval.\n");
        fprintf(stderr, "Try %s --help for more information.\n", argv[0]);
        exit(1);
    }

    if (!config.no_auth_warning && config.auth != NULL)
    {
        fputs("Warning: Using a password with '-a' or '-u' option on the command"
              " line interface may not be safe.\n",
              stderr);
    }

    return i;
}

static void parseEnv()
{
    /* Set auth from env, but do not overwrite CLI arguments if passed */
    char *auth = getenv(REDIS_CLI_AUTH_ENV);
    if (auth != NULL && config.auth == NULL)
    {
        config.auth = auth;
    }
}

/* Connect to the server. It is possible to pass certain flags to the function:
 *      CC_FORCE: The connection is performed even if there is already
 *                a connected socket.
 *      CC_QUIET: Don't print errors if connection fails. */
static int cliConnect(int flags)
{
    //如果连接不存在 或者 强制连接
    if (context == NULL || flags & CC_FORCE)
    {
        if (context != NULL)
        {
            redisFree(context);
        }

        if (config.hostsocket == NULL)
        {
            context = redisConnect(config.hostip, config.hostport);
        }
        else
        {
            context = redisConnectUnix(config.hostsocket);
        }

        if (context->err)
        {
            if (!(flags & CC_QUIET))
            {
                fprintf(stderr, "Could not connect to Redis at ");
                if (config.hostsocket == NULL)
                    fprintf(stderr, "%s:%d: %s\n",
                            config.hostip, config.hostport, context->errstr);
                else
                    fprintf(stderr, "%s: %s\n",
                            config.hostsocket, context->errstr);
            }
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
        }

        /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
         * in order to prevent timeouts caused by the execution of long
         * commands. At the same time this improves the detection of real
         * errors. */
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

        /* Do AUTH and select the right DB. */
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
    }
    return REDIS_OK;
}

/*------------------------------------------------------------------------------
 * Program main()
 *--------------------------------------------------------------------------- */

int main(int argc, char **argv) {
    int firstarg;
    //SDS（Simple Dynamic Strings）是一个C语言字符串库，设计中增加了从堆上分配内存的字符串，来扩充有限的libc字符处理的功能
    config.hostip = sdsnew("127.0.0.1");
    config.hostport = 6379;
    config.hostsocket = NULL;
    config.repeat = 1;
    config.interval = 0;
    config.dbnum = 0;
    config.interactive = 0;
    config.shutdown = 0;
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
    config.latency_mode = 0;
    config.latency_dist_mode = 0;
    config.latency_history = 0;
    config.lru_test_mode = 0;
    config.lru_test_sample_size = 0;
    config.cluster_mode = 0;
    config.slave_mode = 0;
    config.getrdb_mode = 0;
    config.stat_mode = 0;
    config.scan_mode = 0;
    config.intrinsic_latency_mode = 0;
    config.pattern = NULL;
    config.rdb_filename = NULL;
    config.pipe_mode = 0;
    config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
    config.bigkeys = 0;
    config.hotkeys = 0;
    config.stdinarg = 0;
    config.auth = NULL;
    config.eval = NULL;
    config.eval_ldb = 0;
    config.eval_ldb_end = 0;
    config.eval_ldb_sync = 0;
    config.enable_ldb_on_eval = 0;
    config.last_cmd_type = -1;
    config.verbose = 0;
    config.no_auth_warning = 0;
    config.cluster_manager_command.name = NULL;
    config.cluster_manager_command.argc = 0;
    config.cluster_manager_command.argv = NULL;
    config.cluster_manager_command.flags = 0;
    config.cluster_manager_command.replicas = 0;
    config.cluster_manager_command.from = NULL;
    config.cluster_manager_command.to = NULL;
    config.cluster_manager_command.weight = NULL;
    config.cluster_manager_command.weight_argc = 0;
    config.cluster_manager_command.slots = 0;
    config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
    config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
    config.cluster_manager_command.threshold = CLUSTER_MANAGER_REBALANCE_THRESHOLD;
    pref.hints = 1;

    spectrum_palette = spectrum_palette_color;
    spectrum_palette_size = spectrum_palette_color_size;

    if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
        config.output = OUTPUT_RAW;
    else
        config.output = OUTPUT_STANDARD;
    config.mb_delim = sdsnew("\n");
    //解析参数
    firstarg = parseOptions(argc,argv);
    argc -= firstarg;
    argv += firstarg;

    parseEnv();

    /* Otherwise, we have some arguments to execute */
    if (cliConnect(0) != REDIS_OK) exit(1);
    
    
    if (config.eval) {
        return evalMode(argc,argv);
    } else {
        return noninteractive(argc,convertToSds(argc,argv));
    }
}
